<template>
  <div class="main">

    <div class="video-area">
      <p class="area-title">视频区</p>
      <video ref="localVideo"></video>
      <video ref="remoteVideo"></video>
      <p>
        <button @click="initServer" :disabled="link">连接服务器</button>
        <label><span>输入用户名</span><input type="text" v-model.trim="user" :disabled="!link"></label>
        <button @click="signin" :disabled="link ? (login ? true : false) : true">登录</button>
        <label><span>输入对方用户名</span><input type="text" v-model.trim="target" :disabled="!login"></label>
        <button @click="search" :disabled="!login">查找用户是否存在</button>
        <button @click="activeCall" :disabled="!callout">拨打</button>
        <button @click="initReply" :disabled="!callin">接听</button>
        <button @click="initRefuse" :disabled="!callin">拒绝</button>
        <button @click="handleDiscon(true)" :disabled="!phonecall">断开</button>
      </p>
      <!-- 日志 -->
      <ul>
        <li v-for="item, index in logs" :key="index">{{ item }}</li>
      </ul>
    </div>

    <div class="chat-area">
      <p class="area-title">聊天区</p>
      <ul class="news-list">
        <li v-for="item, index in news" :key="index">
          <p v-if="item.type" class="left">
            <i class="head"></i>
          <div class="box">
            <span class="name">{{ item.name }}</span>
            <div>{{ item.message }}</div>
          </div>
          </p>
          <p v-else class="right">
          <div class="box">
            <span class="name">{{ item.name }}</span>
            <div>{{ item.message }}</div>
          </div>
          <i class="head"></i>
          </p>
        </li>
      </ul>
      <p class="bottom">
        <input type="text" :disabled="!login">
        <button :disabled="!login">发送</button>
      </p>
    </div>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
const localVideo = ref<HTMLVideoElement>();
const remoteVideo = ref<HTMLVideoElement>();
const peer = ref<RTCPeerConnection>();
// 链接到服务器
const link = ref(false);
// 呼出
const callout = ref(false);
// 呼入
const callin = ref(false);
// 通话中
const phonecall = ref(false);
// 是否登录
const login = ref(false);
// 当前用户
const user = ref("");
// 被呼叫目标
const target = ref("");
// 链接管理
const socket = ref<WebSocket>();
// 呼叫者
const caller = ref("");
// 本地媒体流
const streams = ref<MediaStream>();
// 存储发送的ice信息
const ices: RTCIceCandidate[] = [];
// 初始化服务器链接
const initServer = () => {
  socket.value = new WebSocket('ws://localhost:8080');
  socket.value.addEventListener("open", () => link.value = true);
  socket.value.addEventListener("message", hanldeSocketMessage)
  socket.value.addEventListener("close", () => {
    link.value = false;
    showMessage("链接服务器失败！");
  });
  socket.value.addEventListener("error", () => {
    link.value = false;
    showMessage("服务器链接报错啦！");
  });
}
const logs = reactive<string[]>([])
// 消息列表
const news = [
  {
    name: "张三",
    type: 0,
    message: "12312312"
  },
  {
    name: "李四",
    type: 1,
    message: "12312312"
  },
  {
    name: "张三",
    type: 0,
    message: "12312312"
  },
  {
    name: "李四",
    type: 1,
    message: "12312312"
  },
  {
    name: "张三",
    type: 0,
    message: "12312312"
  },
  {
    name: "李四",
    type: 1,
    message: "12312312"
  },
  {
    name: "张三",
    type: 0,
    message: "12312312"
  },
  {
    name: "李四",
    type: 1,
    message: "12312312"
  },
  {
    name: "张三",
    type: 0,
    message: "12312312"
  },
  {
    name: "李四",
    type: 1,
    message: "12312312"
  },
  {
    name: "张三",
    type: 0,
    message: "12312312"
  },
  {
    name: "李四",
    type: 1,
    message: "12312312"
  },
]

// 登录,链接到服务器
const signin = () => {
  if (user.value) {
    send("login", user.value);
  } else {
    showMessage("请输入登录用户的id");
  }
}

// 向服务器发送消息
const send = (type: string, value: any) => {
  socket.value?.send(JSON.stringify({ event: type, value: value }));
}

// 处理服务器返回的事件
const hanldeSocketMessage = async (MessageEvent: MessageEvent) => {
  const { event, value, message, person } = JSON.parse(MessageEvent.data);
  if (event === "link") {
    // 链接服务器成功,启用通话功能按钮
    link.value = true;
    showMessage(message);
  } else if (event === 'login') {
    // 登录成功,启用通话功能按钮
    login.value = value;
    showMessage(message);
  } else if (event === 'call') {
    handleinvite(value, person, message)
  } else if (event === 'search') {
    handleSearch(value, message)
  } else if (event === 'accept') {
    handleAcceptCall(message);
  } else if (event === 'refuse') {
    handleDiscon(false, message);
  } else if (event === 'offline') {
    handleDiscon(false, message);
  } else if (event === 'disconnect') {
    handleDiscon(false, message);
  } else if (event === 'ice') {
    handleIce(value, message)
  } else if (event === 'offer') {
    acceptOffer(value, message)
  } else if (event === 'answer') {
    acceptAnswer(value, message);
  } else if (event === 'ice-end') {
    addIce();
  } else {
    showMessage(message);
  }
}

// 查找对应用户是否能呼叫
const search = () => {
  if (target.value) {
    if (target.value === user.value) {
      showMessage("不能查找自己");
    } else {
      send("search", target.value);
    }
  } else {
    showMessage("请输入被呼叫用户的id");
  }
}

// 处理查找结果
const handleSearch = (value: boolean, message: string) => {
  callout.value = value;
  showMessage(message);
}

// 主动拨打电话
const activeCall = async () => {
  callout.value = false;
  phonecall.value = true;
  await getMediaStream();
  createPeer();
  send("call", { caller: user.value, target: target.value });
}

// 处理来电话
const handleinvite = (value: boolean, person: string, message: string) => {
  callin.value = value;
  callout.value = false;
  phonecall.value = false;
  caller.value = person;
  showMessage(message);
}

// 对方接听电话
const handleAcceptCall = (message: string) => {
  phonecall.value = true;
  callout.value = false;
  callin.value = false;
  createOffer();
  showMessage(message);
}

// 我方主动接听电话
const initReply = async () => {
  phonecall.value = true;
  callout.value = false;
  callin.value = false;
  await getMediaStream();
  createPeer();
  send('accept', { user: user.value, caller: caller.value });
}


// 处理断开通话
const handleDiscon = (mode: boolean, message?: string) => {
  callout.value = true;
  callin.value = false;
  phonecall.value = false;
  peer.value?.close();
  showMessage(message || "通信已断开");
  mode && send("disconnect", { user: user.value, target: target.value })
}

// 处理拒绝通话
const initRefuse = () => {
  callout.value = true;
  callin.value = false;
  send("refuse", { user: user.value, target: caller.value })
}

// 获取媒体流
const getMediaStream = async () => {
  try {
    showMessage("我方开始获取本地媒体流");
    const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
    localVideo.value!.srcObject = stream;
    localVideo.value?.play();
    streams.value = stream;
    showMessage("我方的媒体流已就绪");
  } catch (error) {
    showMessage("未获取到媒体信息" + error);
  }
}

// 创建本地连接对象
const createPeer = () => {
  try {
    const instance = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
    showMessage("开始创建本地实例");
    instance.addEventListener("icecandidate", ({ candidate }) => {
      if (candidate) {
        showMessage("我方收集到ice信息");
        // instance.addIceCandidate(new RTCIceCandidate(candidate));
        send('ice', { user: user.value, target: target.value || caller.value, value: candidate });
        showMessage("我方已发送ice信息");
      }
    });

    instance.addEventListener("track", e => {
      showMessage("对方的媒体流来了");
      const data = e.streams[0];
      // 确保新流加载完成后再播放
      remoteVideo.value!.addEventListener("loadedmetadata", () => {
        remoteVideo.value!.play()
      })
      remoteVideo.value!.srcObject = data
    })
    peer.value = instance;
    streams.value?.getTracks().forEach(track => instance.addTrack(track, streams.value!));
    showMessage("实例创建完成,本地媒体流已添加");
  } catch (error) {
    console.log(error);
  }
}


// 生成本地媒体协商信息邀约
const createOffer = async () => {
  if (peer.value) {
    const offer = await peer.value.createOffer()
    await peer.value.setLocalDescription(offer);
    send('offer', { user: user.value, target: target.value, value: offer });
  }
}

// 接收对方的媒体协商信息答复
const acceptAnswer = async (answer: RTCSessionDescriptionInit, message: string) => {
  showMessage(message);
  if (peer.value) {
    await peer.value.setRemoteDescription(new RTCSessionDescription(answer));
    addIce()
  }
}

// 接收对方的媒体协商信息邀约
const acceptOffer = async (offer: RTCSessionDescriptionInit, message: string) => {
  showMessage(message);
  if (peer.value) {
    await peer.value.setRemoteDescription(new RTCSessionDescription(offer));
    const answer = await peer.value.createAnswer();
    await peer.value.setLocalDescription(answer);
    addIce()
    send('answer', { user: user.value, target: caller.value, value: answer });
  }
}

// 处理网络协商信息
const handleIce = (candidate: RTCIceCandidate, message: string) => {
  showMessage(message);
  ices.push(candidate)
}

// ice信息等待添加完成在添加
const addIce = () => {
  showMessage("开始处理ice");
  ices.forEach(e => {
    if (peer.value) {
      peer.value.addIceCandidate(e);
    }
  })
}

// 显示消息
const showMessage = (message: string) => logs.push(message);
</script>

<style>
.main {
  display: flex;
  justify-content: space-between;
  align-items: stretch;
  padding: 10px;
  width: 100%;
  height: 100%;
}

.video-area,
.chat-area {
  border: 1px solid #000;
  padding: 10px;
}

.chat-area {
  width: 26%;
  display: flex;
  flex-direction: column;
}

.area-title {
  text-align: center;
}

.news-list {
  flex: 1;
  padding-left: 0;
  border: 1px solid #000;
  overflow-y: auto;
}

li {
  list-style: none;
  padding: 0 10px;
}

.left {
  text-align: left;
}

.left i {
  margin-right: 10px;
}

.head {
  display: inline-block;
  width: 30px;
  height: 30px;
  border: 1px solid #000;
  border-radius: 4px;
}

.box {
  display: inline-block;
  width: 40%;
}

.right {
  text-align: right;
}

.right i {
  margin-left: 10px;
}

.chat-area .bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.chat-area .bottom input {
  flex: 1;
  height: 30px;
}

.chat-area .bottom button {
  width: 120px;
}

video {
  border: 1px solid #000;
  width: 600px;
  height: 400px;
  object-fit: fill;
}

video+video {
  margin-left: 20px;
}

button {
  padding: 5px 10px;
  margin-right: 10px;
  vertical-align: middle;
}

input {
  width: 80px;
  height: 20px;
  line-height: 20px;
  padding: 0;
  box-sizing: border-box;
  vertical-align: middle;
  margin-left: 10px;
  margin-right: 10px;
}

ul {
  padding-left: 0;
}
</style>
